﻿using System.Runtime.InteropServices;
using OnDash.Mathematics.Data;

namespace OnDash.Mathematics.Vector;

public struct Vector2(float x, float y) : IEquatable<Vector2>
{
    public float X = x;

    public float Y = y;

    public Vector2(float value) : this(value, value)
    {
    }

    public Vector2(Vector2 value) : this(value.X, value.Y)
    {
    }

    public Vector2 Xy
    {
        get => new(X, Y);
        set
        {
            X = value.X;
            Y = value.Y;
        }
    }

    public Vector2 Yx
    {
        get => new(Y, X);
        set
        {
            Y = value.X;
            X = value.Y;
        }
    }

    public float this[int index]
    {
        get
        {
            return index switch
            {
                0 => X,
                1 => Y,
                _ => throw new IndexOutOfRangeException($"{index} is out of range.")
            };
        }
        set
        {
            switch (index)
            {
                case 0:
                    X = value;
                    break;
                case 1:
                    Y = value;
                    break;
                default:
                    throw new IndexOutOfRangeException($"{index} is out of range.");
            }
        }
    }

    public float Length => MathHelper.Sqrt(X * X + Y * Y);

    public float LengthSquared => X * X + Y * Y;

    public Vector2 PerpendicularRight => new(Y, -X);

    public Vector2 PerpendicularLeft => new(-Y, X);

    public void Normalize()
    {
        var scale = MathHelper.InverseSqrtFast(LengthSquared);
        X *= scale;
        Y *= scale;
    }

    public Vector2 Normalized()
    {
        var v = this;
        v.Normalize();
        return v;
    }

    public static Vector2 UnitX = new(1, 0);
    public static Vector2 UnitY = new(0, 1);

    public static Vector2 Zero = new(0, 0);
    public static Vector2 One = new(1, 1);

    public static int SizeInBytes = Marshal.SizeOf(new Vector2());

    public static Vector2 Add(Vector2 left, Vector2 right)
        => new(left.X + right.X, left.Y + right.Y);

    public static Vector2 Subtract(Vector2 left, Vector2 right)
        => new(left.X - right.X, left.Y - right.Y);

    public static Vector2 Multiply(Vector2 left, float right)
        => new(left.X * right, left.Y * right);

    public static Vector2 Multiply(Vector2 left, Vector2 right)
        => new(left.X * right.X, left.Y * right.Y);

    public static Vector2 Divide(Vector2 left, float right)
        => new(left.X / right, left.Y / right);

    public static Vector2 Divide(Vector2 left, Vector2 right)
        => new(left.X / right.X, left.Y / right.Y);

    public static Vector2 Min(Vector2 left, Vector2 right)
        => left.LengthSquared < right.LengthSquared ? left : right;

    public static Vector2 Max(Vector2 left, Vector2 right)
        => left.LengthSquared >= right.LengthSquared ? left : right;

    public static Vector2 ComponentMin(Vector2 left, Vector2 right)
    {
        return new Vector2(
            left.X < right.X ? left.X : right.X,
            left.Y < right.Y ? left.Y : right.Y
        );
    }

    public static Vector2 ComponentMax(Vector2 left, Vector2 right)
    {
        return new Vector2(
            left.X > right.X ? left.X : right.X,
            left.Y > right.Y ? left.Y : right.Y
        );
    }

    public static Vector2 MagnitudeMin(Vector2 left, Vector2 right)
        => left.LengthSquared < right.LengthSquared ? left : right;

    public static Vector2 MagnitudeMax(Vector2 left, Vector2 right)
        => left.LengthSquared >= right.LengthSquared ? left : right;

    public static Vector2 Clamp(Vector2 value, Vector2 min, Vector2 max)
    {
        return new Vector2(
            value.X < min.X ? min.X : value.X > max.X ? max.X : value.X,
            value.Y < min.Y ? min.Y : value.Y > max.Y ? max.Y : value.Y
        );
    }

    public static float Distance(Vector2 value1, Vector2 value2)
        => MathHelper.Sqrt(DistanceSquared(value1, value2));

    public static float DistanceSquared(Vector2 value1, Vector2 value2)
        => (value2.X - value1.X) * (value2.X - value1.X) + (value2.Y - value1.Y) * (value2.Y - value1.Y);

    public static Vector2 Normalize(Vector2 value)
        => new(value.X / value.Length, value.Y / value.Length);

    public static Vector2 NormalizeFast(Vector2 value)
    {
        return new Vector2(
            value.X * MathHelper.InverseSqrtFast(value.X * value.X + value.Y * value.Y),
            value.Y * MathHelper.InverseSqrtFast(value.X * value.X + value.Y * value.Y)
        );
    }

    public static float Dot(Vector2 left, Vector2 right)
        => left.X * right.X + left.Y * right.Y;

    public static float PerpDot(Vector2 left, Vector2 right)
        => left.X * right.Y - left.Y * right.X;

    public static Vector2 Lerp(Vector2 start, Vector2 end, float amount)
    {
        return new Vector2(
            amount * (end.X - start.X) + start.X,
            amount * (end.Y - start.Y) + start.Y
        );
    }

    public static Vector2 Negate(Vector2 value)
        => new(-value.X, -value.Y);

    public static Vector2 BaryCentric(Vector2 a, Vector2 b, Vector2 c, float u, float v)
        => a + u * (b - a) + v * (c - a);

    public static Vector2 Transform(Vector2 vector, Quaternion rotation)
    {
        var v = new Quaternion(vector.X, vector.Y, 0, 0);
        var i = Quaternion.Invert(rotation);
        var t = Quaternion.Multiply(rotation, v);
        v = Quaternion.Multiply(t, i);

        return new Vector2(v.X, v.Y);
    }

    public static Vector2 operator +(Vector2 left, Vector2 right) => Add(left, right);
    public static Vector2 operator -(Vector2 left, Vector2 right) => Subtract(left, right);
    public static Vector2 operator -(Vector2 value) => Negate(value);
    public static Vector2 operator *(Vector2 left, float right) => Multiply(left, right);
    public static Vector2 operator *(float left, Vector2 right) => Multiply(right, left);
    public static Vector2 operator *(Vector2 left, Vector2 right) => Multiply(left, right);
    public static Vector2 operator /(Vector2 left, float right) => Divide(left, right);
    public static Vector2 operator /(Vector2 left, Vector2 right) => Divide(left, right);
    public static bool operator ==(Vector2 left, Vector2 right) => left.Equals(right);
    public static bool operator !=(Vector2 left, Vector2 right) => !(left == right);

    public override string ToString()
        => string.Format("{0}{2}, {1}", X, Y, MathHelper.ListSeparator);

    public override int GetHashCode() => HashCode.Combine(X, Y);

    public override bool Equals(object? obj) => obj is Vector2 other && Equals(other);

    public bool Equals(Vector2 other)
        => X.Equals(other.X) &&
           Y.Equals(other.Y);
}